
/* csdn: 专题讲解
 * https://blog.csdn.net/ldl617/category_11006699.html
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <errno.h>

struct buffer {
	void * start;
	size_t length;
};

struct buffer *buffers = NULL;

int main(int argc, char **argv)
{
	int fd, i, r;
	FILE *file_fd;
	fd_set fds;
	struct timeval tv;
	struct v4l2_capability cap;
	struct v4l2_input inp;
	struct v4l2_fmtdesc fmtdesc;
	struct v4l2_format fmt;
	struct v4l2_requestbuffers req;
	struct v4l2_buffer buf;
	enum v4l2_buf_type type;


	if (argc != 4) {
		printf("Usage: v4l2_test <device> <frame_num> <save_file>\n");
		printf("example: v4l2_test /dev/video0 10 test.yuv\n");
		goto err1;
	}

	fd = open(argv[1], O_RDWR);

	if (fd < 0) {
		printf("open device: %s fail\n", argv[1]);
		goto err1;
	}

	file_fd = fopen(argv[3], "wb+");
	if (!file_fd) {
		printf("open save_file: %s fail\n", argv[3]);
		goto err1;
	}

	if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
		printf("Get video capability error!\n");
		goto err;
	}

	if (!(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) {
		printf("Video device not support capture!\n");
		goto err;
	}

	printf("Support capture!\n");

	inp.index = 0;
	while (ioctl (fd, VIDIOC_ENUMINPUT, &inp) == 0) {
		printf("%s : std: %08x\n", inp.name, inp.std);
		inp.index++;
	}

	inp.index = 1;

	if (ioctl (fd, VIDIOC_S_INPUT, &inp) < 0) {
		printf("Set input fail\n");
		goto err;
	}

	fmtdesc.index = 0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) {
		printf("fmt name: [%s]\n", fmtdesc.description);
		printf("fmt pixelformat : '%c%c%c%c', description = '%s'\n",fmtdesc.pixelformat & 0xFF,
				(fmtdesc.pixelformat >> 8) & 0xFF,(fmtdesc.pixelformat >> 16) & 0xFF,
				(fmtdesc.pixelformat >> 24) & 0xFF,fmtdesc.description);
		fmtdesc.index++;
	}

	memset(&fmt, 0, sizeof(struct v4l2_format));
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	fmt.fmt.pix.width = 1920;
	fmt.fmt.pix.height = 1080;
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
	fmt.fmt.pix.field = V4L2_FIELD_ANY;

	if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
		printf("Set format fail\n");
		goto err;
	}

	req.count = 5;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;
	if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
		printf("Reqbufs fail\n");
		goto err;
	}

	printf("buffer number: %d\n", req.count);

	buffers = calloc (req.count, sizeof (*buffers));

	for(i = 0; i < req.count; i++) {
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) {
			printf("Querybuf fail\n");
			goto err;
		}

		printf("buffer[%d]: length = %d, offset = %d\n", i, buf.length, buf.m.offset);
		buffers[i].length = buf.length;
		buffers[i].start =
			mmap (NULL /* start anywhere */,
					buf.length,
					PROT_READ | PROT_WRITE /* required */,
					MAP_SHARED /* recommended */,
					fd, buf.m.offset);

		if (MAP_FAILED == buffers[i].start) {
			printf ("mmap failed\n");
			req.count = i;
			goto unmmap;
		}
	}

	for (i = 0; i < req.count; ++i)
	{
		memset(&buf, 0, sizeof(buf));

		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory      = V4L2_MEMORY_MMAP;
		buf.index       = i;

		if (ioctl (fd, VIDIOC_QBUF, &buf) < 0)
			printf ("VIDIOC_QBUF failed\n");

		printf("buf[%d].flags = 0x%08x\n", i, buf.flags);
	}


	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
		printf ("VIDIOC_STREAMON failed\n");
	int num = 0;
	while (1)
	{
		FD_ZERO (&fds);
		FD_SET(fd, &fds);
		tv.tv_sec = 5;
		tv.tv_usec = 0;

		r = select (fd + 1, &fds, NULL, NULL, &tv);

		if (-1 == r)
		{
			if (EINTR == errno)
				continue;
			printf ("select err\n");
		}
		if (0 == r)
		{
			fprintf (stderr, "select timeout\n");
			exit (EXIT_FAILURE);
		}

		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		if (ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
			printf("dqbuf fail\n");

		fwrite(buffers[buf.index].start, buf.bytesused, 1, file_fd);

		if (ioctl (fd, VIDIOC_QBUF, &buf) < 0)
			printf("failture VIDIOC_QBUF\n");
		num++;
		if(num > atoi(argv[2]))
			break;
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
		printf("VIDIOC_STREAMOFF fail\n");
unmmap:
	for (i = 0; i < req.count; ++i)
		if (-1 == munmap (buffers[i].start, buffers[i].length))
			printf ("munmap error");

	close (fd);
	fclose (file_fd);
	return 0;
err:
	close(fd);
	fclose (file_fd);
err1:
	return -1;
}
